package io.noties.markwon.plugin.tables

import android.text.Spanned
import android.widget.TextView
import io.noties.markwon.base.AbstractMarkwonPlugin
import io.noties.markwon.base.MarkwonVisitor
import io.noties.markwon.plugin.tables.TableRowsScheduler.schedule
import io.noties.markwon.plugin.tables.TableRowsScheduler.unschedule
import io.noties.markwon.plugin.tables.span.TableRowSpan
import io.noties.markwon.plugin.tables.span.TableSpan
import org.commonmark.ext.gfm.tables.TableBlock
import org.commonmark.ext.gfm.tables.TableBody
import org.commonmark.ext.gfm.tables.TableCell
import org.commonmark.ext.gfm.tables.TableHead
import org.commonmark.ext.gfm.tables.TableRow
import org.commonmark.ext.gfm.tables.TablesExtension
import org.commonmark.node.Node
import org.commonmark.parser.Parser

/**
 * @since 3.0.0
 */
class TablePlugin internal constructor() : AbstractMarkwonPlugin() {
    //private final TableTheme theme;
    private val visitor: TableVisitor = TableVisitor()

    override fun configureParser(builder: Parser.Builder) {
        builder.extensions(setOf(TablesExtension.create()))
    }

    override fun configureVisitor(builder: MarkwonVisitor.Builder) {
        visitor.configure(builder)
    }

    override fun beforeRender(node: Node) {
        // clear before rendering (as visitor has some internal mutable state)
        visitor.clear()
    }

    override fun beforeSetText(textView: TextView, markdown: Spanned) {
        unschedule(textView)
    }

    override fun afterSetText(textView: TextView) {
        schedule(textView)
    }

    private class TableVisitor internal constructor() {
        //private final MarkwonTheme tableTheme;
        private var pendingTableRow: MutableList<TableRowSpan.Cell>? = null
        private var tableRowIsHeader = false
        private var tableRows = 0

        /*TableVisitor(@NonNull MarkwonTheme tableTheme) {
            this.tableTheme = tableTheme;
        }*/
        fun clear() {
            pendingTableRow = null
            tableRowIsHeader = false
            tableRows = 0
        }

        fun configure(builder: MarkwonVisitor.Builder) {
            builder // @since 4.1.1 we use TableBlock instead of TableBody to add new lines
                .on(TableBlock::class.java, object : MarkwonVisitor.NodeVisitor<TableBlock> {
                    override fun visit(visitor: MarkwonVisitor, n: TableBlock) {
                        visitor.blockStart(n)
                        val length = visitor.length()
                        visitor.visitChildren(n)

                        // @since 4.3.1 apply table span for the full table
                        visitor.setSpans(length, TableSpan())
                        visitor.blockEnd(n)
                    }
                })
                .on(TableBody::class.java, object : MarkwonVisitor.NodeVisitor<TableBody> {
                    override fun visit(visitor: MarkwonVisitor, n: TableBody) {
                        visitor.visitChildren(n)
                        tableRows = 0
                    }
                })
                .on(TableRow::class.java, object : MarkwonVisitor.NodeVisitor<TableRow> {
                    override fun visit(visitor: MarkwonVisitor, n: TableRow) {
                        visitRow(visitor, n)
                    }
                })
                .on(TableHead::class.java, object : MarkwonVisitor.NodeVisitor<TableHead> {
                    override fun visit(visitor: MarkwonVisitor, n: TableHead) {
                        visitRow(visitor, n)
                    }
                })
                .on(TableCell::class.java, object : MarkwonVisitor.NodeVisitor<TableCell> {
                    override fun visit(visitor: MarkwonVisitor, n: TableCell) {
                        val length = visitor.length()
                        visitor.visitChildren(n)
                        if (pendingTableRow == null) {
                            pendingTableRow = ArrayList(2)
                        }
                        pendingTableRow!!.add(
                            TableRowSpan.Cell(
                                tableCellAlignment(n.alignment),
                                visitor.builder().removeFromEnd(length)
                            )
                        )
                        tableRowIsHeader = n.isHeader
                    }
                })
        }

        private fun visitRow(visitor: MarkwonVisitor, node: Node) {
            val length = visitor.length()
            visitor.visitChildren(node)
            if (pendingTableRow != null) {
                val builder = visitor.builder()

                // @since 2.0.0
                // we cannot rely on hasNext(TableHead) as it's not reliable
                // we must apply new line manually and then exclude it from tableRow span
                val addNewLine: Boolean
                run {
                    val builderLength = builder.length
                    addNewLine = (builderLength > 0
                            && '\n' != builder[builderLength - 1])
                }
                if (addNewLine) {
                    visitor.forceNewLine()
                }

                // @since 1.0.4 Replace table char with non-breakable space
                // we need this because if table is at the end of the text, then it will be
                // trimmed from the final result
                builder.append('\u00a0')
                val span: Any = TableRowSpan(
                    visitor.configuration().theme,
                    pendingTableRow!!,
                    tableRowIsHeader,
                    tableRows % 2 == 1
                )
                tableRows = if (tableRowIsHeader) 0 else tableRows + 1
                visitor.setSpans(if (addNewLine) length + 1 else length, span)
                pendingTableRow = null
            }
        }

        companion object {
            @TableRowSpan.Alignment
            private fun tableCellAlignment(alignment: TableCell.Alignment?): Int {
                val out: Int = if (alignment != null) {
                    when (alignment) {
                        TableCell.Alignment.CENTER -> TableRowSpan.ALIGN_CENTER
                        TableCell.Alignment.RIGHT -> TableRowSpan.ALIGN_RIGHT
                        else -> TableRowSpan.ALIGN_LEFT
                    }
                } else {
                    TableRowSpan.ALIGN_LEFT
                }
                return out
            }
        }
    }

    companion object {
        fun create(): TablePlugin {
            return TablePlugin()
        }
    }
}